package com.baiytfp.hf.HFDataExchange.common.base.shiro;

import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.DefaultSessionKey;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.WebUtils;

import com.baiytfp.hf.HFDataExchange.common.utils.CommonUtil;
import com.baiytfp.hf.baiytfp.framework.shiro.ShiroUtil;
import com.baiytfp.hf.baiytfp.framework.util.CacheUtil;
import com.baiytfp.hf.baiytfp.framework.util.SerializeUtil;
import com.baiytfp.hf.baiytfp.framework.util.SpringUtil;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.Serializable;
import java.util.Deque;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.Set;
/**
 * 
 * @Title: KickoutSessionControlFilter.java
 * @Package com.agood.bejavagod.controller.filter
 * @Description: 同一用户后登陆踢出前面的用户
 * @version V1.0
 */
public class DeviceSessionControlFilter  extends AccessControlFilter {
	
		public static final String LOGIN_OUT_KEY="_$_login_out_key";
		

	    private String outUrl; //踢出后到的地址
	    private boolean outAfter = false; //踢出之前登录的/之后登录的用户 默认踢出之前登录的用户
	    private int maxUserSession = 1; //同一个帐号最大会话数 默认1
	    private int maxOnlineSessions = 300;//最大在线人数

	    private SessionManager sessionManager; //会话管理器
	    

	    public void setOutUrl(String kickoutUrl) {
	        this.outUrl = kickoutUrl;
	    }

	    public void setOutAfter(boolean kickoutAfter) {
	        this.outAfter = kickoutAfter;
	    }

	    public void setMaxUserSession(int maxSession) {
	        this.maxUserSession = maxSession;
	    }
	    
	    public void setMaxOnlineSessions(int maxSession) {
	        this.maxOnlineSessions = maxSession;
	    }

	    public void setSessionManager(SessionManager sessionManager) {
	        this.sessionManager = sessionManager;
	    }

	    

	    @Override
	    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
	        return false;
	    }

	    @Override
	    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
	    	Subject subject = getSubject(request, response); //获取认证信息
	        if(!subject.isAuthenticated() && !subject.isRemembered()) {
	            //如果没有登录，直接进行之后的流程
	            return true;
	        }
	        Session session = subject.getSession();//获取会话
	        String username =(String) subject.getPrincipal();//获取登录信息
	        
	        boolean rs = doFilter(request, response, session, subject, isMaxOnlineSessions(username),-2);
	        if(rs) {
	        	// 同步控制
		        Deque<Serializable> deque = getDeque(username); //不存在则重新设置
		        if(deque == null) {
		            deque = new LinkedList<Serializable>();
		        }
		        //踢人
		        markExpire(deque, session);
		        //更新缓存
		        updateCache(username, deque);
		        
	        	rs = doFilter(request, response, session, subject,!CommonUtil.isEmpty(session.getAttribute(LOGIN_OUT_KEY)), -3);
	        }
	        return rs;
	    }
	    
	    private void markExpire(Deque<Serializable> deque, Session session) {
	    	Serializable sessionId = session.getId();//获取会话id
	    	 //如果队列里没有此sessionId，且用户没有被踢出；放入队列
	        if(!deque.contains(sessionId) && session.getAttribute(LOGIN_OUT_KEY) == null) {
	            deque.push(sessionId);
	        }

	        //如果队列里的sessionId数超出最大会话数，开始踢人
	        while(deque.size() > maxUserSession) {
	            Serializable outSessionId = null;
	            if(outAfter) { //如果踢出后者
	                outSessionId = deque.removeFirst();
	            } else { //否则踢出前者
	                outSessionId = deque.removeLast();
	            }
	            try {
	                Session outSession = sessionManager.getSession(new DefaultSessionKey(outSessionId));
	                if(outSession != null) {
	                    //设置会话的kickout属性表示踢出了
	                    outSession.setAttribute(LOGIN_OUT_KEY, true);
	                }
	            } catch (Exception e) {}
	        }
	    }
	    
	    public boolean isMaxOnlineSessions(String userCode) {
	    	
	    	if(getDeque(userCode) != null) {
	    		return false;
	    	}
	    	int num = getDeviceNum()+1;
	    	System.out.println("login_users: "+num +" \t maxLimit: " +maxOnlineSessions);
	    	return (num) > maxOnlineSessions;
	    }
	    
	    
	    
	    /**
	     * 执行结果
	     * 	如果被踢出了，直接退出，重定向到踢出后的地址
	     * @param request
	     * @param response
	     * @param session
	     * @param subject
	     * @return
	     * @throws IOException
	     */
	    private boolean doFilter(ServletRequest request, ServletResponse response, Session session ,Subject subject ,boolean exctue, int status ) throws IOException {
	    	if ( exctue) {
	    		//会话被踢出了
	            try {
	                subject.logout();
	            } catch (Exception e) { //ignore
	            }
	            saveRequest(request);
	            HttpServletRequest httpRequest = WebUtils.toHttp(request);
	            if (CommonUtil.isAjax(httpRequest)) {
	                HttpServletResponse httpServletResponse = WebUtils.toHttp(response);  
	                httpServletResponse.sendError(440);
	                return false;
	            } else {
	            	
	            	if(outUrl.contains("?")) {
	            		outUrl= outUrl.substring(0, outUrl.indexOf("?"));
	            		outUrl+=  "?status="+status;
	            	}else {
	            		outUrl+="?status="+status;
	            	}
	                WebUtils.issueRedirect(request, response, outUrl);
	                return false;
	            }
	        }
	    	return true;
	    }
	    
	    
	    private Deque<Serializable>  getDeque(String userCode){
	    	Object obj = CacheUtil.getCache().get("_$_DEVICE_LOGIN_INFO$USER:"+userCode);
	    	if( obj != null ) {
	    		return (Deque<Serializable>) SerializeUtil.deserialize((String)obj);
	    	}
	    	return null;
	    }
	    
	    private void updateCache(String userCode , Deque<Serializable> deque) {
	    	CacheUtil.getCache().set("_$_DEVICE_LOGIN_INFO$USER:"+userCode, SerializeUtil.serialize(deque));
	    }
	    
	    private int getDeviceNum(){
	    	Set<Object> obj = CacheUtil.getCache().getAll("_$_DEVICE_LOGIN_INFO$USER:*");
	    	if(obj == null ) {
	    		return 0;
	    	}else {
	    		return obj.size();
	    	}
	    }
	    
	  public static final void cleanDevice(String userCode) {
		  CacheUtil.getCache().del("_$_DEVICE_LOGIN_INFO$USER:"+userCode);
	  }
	    
	    
	}